package org.example.user.projects.dao;

import org.apache.commons.lang.ClassUtils;
import org.example.user.function.ThrowableFunction;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
 * 写点什么？
 *
 * @author hrz
 * @since 2021/3/3 0003
 */
public class DbUtil {

    private static DBConnectManager dbConnectManager;

    private static final Map<Class<?>, String> MAPPING_WRAPPER_CLASS_TO_PREPARE_STATEMENT_METHOD_NAME = new HashMap<>();
    private static final Map<Class<?>, String> MAPPING_WRAPPER_CLASS_TO_RESULT_SET_METHOD_NAME = new HashMap<>();

    static {
        MAPPING_WRAPPER_CLASS_TO_PREPARE_STATEMENT_METHOD_NAME.put(Integer.class, "setInt");
        MAPPING_WRAPPER_CLASS_TO_PREPARE_STATEMENT_METHOD_NAME.put(Long.class, "setLong");
        MAPPING_WRAPPER_CLASS_TO_PREPARE_STATEMENT_METHOD_NAME.put(String.class, "setString");

        MAPPING_WRAPPER_CLASS_TO_RESULT_SET_METHOD_NAME.put(Integer.class, "getInt");
        MAPPING_WRAPPER_CLASS_TO_RESULT_SET_METHOD_NAME.put(String.class, "getString");
        MAPPING_WRAPPER_CLASS_TO_RESULT_SET_METHOD_NAME.put(Long.class, "getLong");
    }

    public static void setDBConnectManager(DBConnectManager dbConnectManager) {
        DbUtil.dbConnectManager = dbConnectManager;
    }

    /**
     * 用于执行DDL语句：create table、alter table等
     */
    public static boolean execute(String sql, Object... args) throws SQLException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Connection connection = dbConnectManager.getConnection();
        PreparedStatement preparedStatement = fillPreparedStatement(connection, sql, args);
        // 如果返回一个ResultSet则返回true，如果返回一个更新的行数或者没有结果返回则返回false，其他情况抛出异常，一般用于创建表等DDL语句才会使用该方法。
        preparedStatement.execute();
        return true;
    }

    /**
     * 用于执行DML语句：insert、update、delete等
     */
    public static boolean executeUpdate(String sql, Object... args) throws SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Connection connection = dbConnectManager.getConnection();
        PreparedStatement preparedStatement = fillPreparedStatement(connection, sql, args);
        // 如果返回一个ResultSet则返回true，如果返回一个更新的行数或者没有结果返回则返回false，其他情况抛出异常，一般用于创建表等DDL语句才会使用该方法。
        int updateCount = preparedStatement.executeUpdate();
        return updateCount > 0;
    }

    /**
     * 用于执行查询语句
     */
    public static <T> T executeQuery(Class<T> tClass, String sql, Object... args) throws Throwable {
        return doExecuteQuery(sql, resultSet -> {
            // 实例化目标对象
            T newInstance = null;
            BeanInfo beanInfo = Introspector.getBeanInfo(tClass, Object.class);
            // 移动游标，遍历每一行数据
            while (resultSet.next()) {
                newInstance = tClass.newInstance();
                // 遍历所有字段，并赋值
                for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
                    String fieldName = propertyDescriptor.getName();
                    Class<?> propertyType = propertyDescriptor.getPropertyType();

                    String methodName = MAPPING_WRAPPER_CLASS_TO_RESULT_SET_METHOD_NAME.get(propertyType);

                    // 我们通过列名去获取数据（列名==字段名）
                    String columnLabel = fieldName;
                    Method method = ResultSet.class.getMethod(methodName, String.class);

                    Object resultValue = method.invoke(resultSet, columnLabel);

                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(newInstance, resultValue);
                }
            }
            return newInstance;
        }, args);
    }

    public static <T> T doExecuteQuery(String sql, ThrowableFunction<ResultSet, T> function, Object... args) throws Throwable {
        Connection connection = dbConnectManager.getConnection();
        PreparedStatement preparedStatement = fillPreparedStatement(connection, sql, args);
        ResultSet resultSet = preparedStatement.executeQuery();
        try {
            return function.apply(resultSet);
        } finally {
            // 释放链接
            dbConnectManager.releaseConnection(connection);
        }
    }

    private static PreparedStatement fillPreparedStatement(Connection connection, String sql, Object... args) throws SQLException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // sql预编译
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        // 遍历参数，并赋值
        int index = 0;
        String methodName;
        Class<?> primitiveClass;
        for (Object arg : args) {
                Class<?> argClass = arg.getClass();

                // 获取包装类的原始类型，例如Integer的原始类型就是int
                primitiveClass = ClassUtils.wrapperToPrimitive(argClass);

                if (primitiveClass == null) {
                    // String.class 没有原始类型
                    primitiveClass = argClass;
                }

                methodName = MAPPING_WRAPPER_CLASS_TO_PREPARE_STATEMENT_METHOD_NAME.get(argClass);
            Method method = PreparedStatement.class.getMethod(methodName, int.class, primitiveClass);
            // 执行setter方法
            method.invoke(preparedStatement, ++index, arg);
        }
        return preparedStatement;
    }
}
